/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
#include "ns3/applications-module.h"
#include "ns3/core-module.h"
#include "ns3/dgrv2-module.h"
#include "ns3/internet-module.h"
#include "ns3/netanim-module.h"
#include "ns3/network-module.h"
#include "ns3/node-list.h"
#include "ns3/point-to-point-module.h"
#include "ns3/topology-read-module.h"
#include "ns3/traffic-control-module.h"

#include <ctime>
#include <fstream>
#include <list>
#include <sstream>
#include <string>

using namespace ns3;

std::string expName = "ddr";
NS_LOG_COMPONENT_DEFINE(expName);

int
main(int argc, char* argv[])
{
    // 初始化实验参数
    std::string topo("abilene");
    std::string format("Inet");
    uint32_t budget(0);
    // 接收方节点编号
    uint32_t sink = 10;
    uint32_t sender = 0;
    uint16_t udpPort = 9;
    uint32_t nPacket = 1000;
    uint32_t packetSize = 1400; // bytes
    // 路由模式（0 none, 1 KShort, 2 DGR, 3 DDR,4 dynamic DDR）
    uint32_t routeSelectMode = 4;

    // 通过命令行修改实验参数
    CommandLine cmd(__FILE__);
    cmd.AddValue("format", "Format to use for data input [Orbis|Inet|Rocketfuel].", format);
    cmd.AddValue("topo", "topology", topo);
    cmd.AddValue("budget", "budget", budget);
    cmd.AddValue("sender", "Node # of sender", sender);
    cmd.AddValue("sink", "Node # of sink", sink);

    cmd.AddValue("EcmpMode",
                 "EcmpMode: (0 none, 1 KShort, 2 DGR, 3 DDR ,4 dynamic DDR)",
                 routeSelectMode);

    cmd.Parse(argc, argv);
    // 设置路由选择模式
    switch (routeSelectMode)
    {
    case 0:
        break;
    case 1:
        Config::SetDefault("ns3::Ipv4DGRRouting::RouteSelectMode", StringValue("KSHORT"));
        break;
    case 2:
        Config::SetDefault("ns3::Ipv4DGRRouting::RouteSelectMode", StringValue("DGR"));
        break;
    case 3:
        Config::SetDefault("ns3::Ipv4DGRRouting::RouteSelectMode", StringValue("DDR"));
        break;
    case 4:
        Config::SetDefault("ns3::Ipv4DGRRouting::RouteSelectMode", StringValue("DYNAMIC_DDR"));
        break;
    default:
        break;
    }

    // 读取拓扑数据
    //  ------------- Read topology data-------------------
    std::string input("contrib/ddr/topo/Inet_" + topo + "_topo.txt");
    TopologyReaderHelper topoHelp;
    topoHelp.SetFileName(input);
    topoHelp.SetFileType(format);
    Ptr<TopologyReader> inFile = topoHelp.GetTopologyReader();
    NodeContainer nodes;
    if (inFile)
    {
        nodes = inFile->Read();
    }
    if (inFile->LinksSize() == 0)
    {
        NS_LOG_ERROR("Problems reading the topology file. Failing.");
        return -1;
    }
    // 创建网络栈和路由
    //  -------- Create nodes and network stacks ---------------
    NS_LOG_INFO("creating internet stack");
    // 网络协议栈
    InternetStackHelper stack;
    // dgr路由协议帮助类 用于配置和安装ddr路由协议
    Ipv4DGRRoutingHelper dgr;
    // 将dgr路由协议添加到列表路由协议中 并设置优先级 数字越小 优先级越高
    Ipv4ListRoutingHelper list;
    list.Add(dgr, 10);
    // 将列表路由协议设置为网络栈的路由协议
    stack.SetRoutingHelper(list);
    // 安装网络协议栈到所有节点
    stack.Install(nodes);

    NS_LOG_INFO("creating ipv4 addresses");
    // 为每个节点分配ip地址 并设置子网掩码
    Ipv4AddressHelper address;
    // 子网掩码 之所以设计成/30 是因为点对点链路每个节点只需要一个ip地址
    // 30掩码可以提供4个ip地址 其中2个可用 2个保留地址（网络地址和广播地址）
    address.SetBase("10.0.0.0", "255.255.255.252");

    // 获取链路总数
    int totlinks = inFile->LinksSize();

    NS_LOG_INFO("creating node containers");
    // 为每个链路创建节点容器 网络设备容器
    NodeContainer* nc = new NodeContainer[totlinks];
    NetDeviceContainer* ndc = new NetDeviceContainer[totlinks];
    // 设置点对点连接和流量控制
    PointToPointHelper p2p;
    TrafficControlHelper tch;
    // 设置队列调度为DGRv2队列调度器
    tch.SetRootQueueDisc("ns3::DGRv2DynamicPriorityQueueDisc");

    // 添加5个内部队列，每个队列容量为1000个数据包

    tch.AddInternalQueues(0,
                          5,
                          "ns3::DropTailQueue<QueueDiscItem>",
                          "MaxSize",
                          StringValue("1000p"));

    NS_LOG_INFO("creating ipv4 interfaces");
    Ipv4InterfaceContainer* ipic = new Ipv4InterfaceContainer[totlinks];
    // std::cout << "totlinks number: " << totlinks << std::endl;
    TopologyReader::ConstLinksIterator iter;
    int i = 0;
    for (iter = inFile->LinksBegin(); iter != inFile->LinksEnd(); iter++, i++)
    {
        // 节点容器
        nc[i] = NodeContainer(iter->GetFromNode(), iter->GetToNode());
        // 将权重作为链路的延迟属性并转换为整数
        std::string delay = iter->GetAttribute("Weight");
        std::stringstream ss;
        ss << delay;
        uint16_t metric; //!< metric in milliseconds
        ss >> metric;
        p2p.SetChannelAttribute("Delay", StringValue(delay + "ms"));
        p2p.SetDeviceAttribute("DataRate", StringValue("100Mbps"));
        // 安装点对点设备
        ndc[i] = p2p.Install(nc[i]);
        tch.Install(ndc[i]);
        ipic[i] = address.Assign(ndc[i]);
        // 为链路的两个节点设置相同的度量值 这里其实就是延迟
        ipic[i].SetMetric(0, metric);
        ipic[i].SetMetric(1, metric);
        address.NewNetwork();
    }
    // 逐链路更新路由表
    Ipv4DGRRoutingHelper::PopulateRoutingTables();

    // -------------------- UDP traffic -----------------
    Ptr<Node> udpSinkNode = nodes.Get(sink);
    Ptr<Ipv4> ipv4UdpSink = udpSinkNode->GetObject<Ipv4>();
    Ipv4InterfaceAddress iaddrUdpSink = ipv4UdpSink->GetAddress(1, 0);
    Ipv4Address ipv4AddrUdpSink = iaddrUdpSink.GetLocal();

    DGRSinkHelper sinkHelper("ns3::UdpSocketFactory",
                             InetSocketAddress(Ipv4Address::GetAny(), udpPort));
    ApplicationContainer sinkApp = sinkHelper.Install(nodes.Get(sink));
    sinkApp.Start(Seconds(0.0));
    sinkApp.Stop(Seconds(4.0));

    // udp sender
    Ptr<Socket> udpSocket = Socket::CreateSocket(nodes.Get(sender), UdpSocketFactory::GetTypeId());
    Ptr<DGRUdpApplication> app = CreateObject<DGRUdpApplication>();
    app->Setup(udpSocket,
               InetSocketAddress(ipv4AddrUdpSink, udpPort),
               packetSize,
               nPacket,
               DataRate("10Mbps"),
               budget,
               true);
    nodes.Get(sender)->AddApplication(app);
    app->SetStartTime(Seconds(1.0));
    app->SetStopTime(Seconds(3.0));

    // // --------------- Net Anim ---------------------
    // AnimationInterface anim (topo + expName + ".xml");
    // std::ifstream topoNetanim (input);
    // std::istringstream buffer;
    // std::string line;
    // getline (topoNetanim, line);
    // for (uint32_t i = 0; i < nodes.GetN (); i ++)
    // {
    //     getline (topoNetanim, line);
    //     buffer.clear ();
    //     buffer.str (line);
    //     int no;
    //     double x, y;
    //     buffer >> no;
    //     buffer >> x;
    //     buffer >> y;
    //     anim.SetConstantPosition (nodes.Get (no), x*10, y*10);
    // }

    // -------- Run the simulation --------------------------
    NS_LOG_INFO("Run Simulation.");
    Simulator::Stop(Seconds(11.0));
    Simulator::Run();
    Simulator::Destroy();

    delete[] ipic;
    delete[] ndc;
    delete[] nc;

    NS_LOG_INFO("Done.");
    return 0;
}
